package asyncLog

import (
	"encoding/json"
	"fmt"
	"github.com/fsnotify/fsnotify"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"runtime"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"time"
)

// LogType 日志对象
type LogType struct {
	files map[string]*LogFile // 下标是文件名

	// 避免并发new对象
	sync.RWMutex
}

// LogFile 日志文件对象
type LogFile struct {
	filename   string // 原始文件名（包含完整目录）
	flag       int    // 默认为log.LstdFlags
	newlinestr string // 换行符

	// 同步设置
	sync struct {
		duration  time.Duration // 同步数据到文件的周期，默认为1秒
		beginTime time.Time     // 开始同步的时间，判断同步的耗时
		status    int32         // 同步状态
	}

	// 日志的等级
	level Priority

	// 缓存
	cache struct {
		use       bool        // 是否使用缓存
		droppable bool        // 缓存满了是否允许丢弃日志
		dropped   int64       // 丢弃统计
		datach    chan string // 缓存队列,无锁实现
	}

	// 文件切割
	logRotate struct {
		rotate LogRotate // 默认按小时切割
		file   *os.File  // 文件操作对象
		fw     *fsnotify.Watcher
		suffix string     // 切割后的文件名后缀
		mutex  sync.Mutex // 文件名锁
	}

	// 日志写入概率
	probability float32
}

// log同步的状态
type syncStatus int32

const (
	statusInit  syncStatus = iota // 初始状态
	statusDoing                   // 同步中
	statusDone                    // 同步已经完成
)

// LogRotate 日志切割的方式
type LogRotate int

const (
	// RotateNone 不切割
	RotateNone LogRotate = iota
	// RotateHour 按小时切割
	RotateHour
	// RotateDate 按日期切割
	RotateDate
)

const (
	// 写日志时前缀的时间格式
	// "2006-01-02T15:04:05Z07:00"
	logTimeFormat string = time.RFC3339

	// 文件写入mode
	fileOpenMode = 0666

	// 文件Flag
	fileFlag = os.O_WRONLY | os.O_CREATE | os.O_APPEND

	// 换行符
	newlineStr  = "\n"
	newlineChar = '\n'

	// 缓存切片的初始容量
	cacheInitCap = 128
)

// 是否需要Flag信息
const (
	NoFlag  = 0
	StdFlag = log.LstdFlags
)

// 异步日志变量
var asyncLog *LogType

var nowFunc = time.Now

func rec(r interface{}) error {

	if r != nil {
		var funcname string
		pc, fromfile, _, ok := runtime.Caller(1)
		if ok {
			pcinfo := runtime.FuncForPC(pc)
			funcname = pcinfo.Name()
		}

		processName := filepath.Base(os.Args[0])
		corefilePath := "/data/core_files/"

		if _, err := os.Stat(corefilePath); os.IsNotExist(err) {
			err = os.MkdirAll(corefilePath, os.ModePerm) // create path
			if err != nil {
				corefilePath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
			}
		}
		corefileName := fmt.Sprintf("panic.%s.%d.%s", processName, os.Getpid(), time.Now().Format("20060102150405"))
		file, err := os.Create(filepath.Join(corefilePath, corefileName))
		if err == nil {
			_, _ = file.WriteString(fmt.Sprintf("-----------\npanic:%v\n", r))
			_, _ = file.WriteString("-----------\npanic from:" + fromfile + "\n")
			_, _ = file.WriteString("-----------\npanic func:" + funcname + "\n")
			_ = file.Sync()
			_, _ = file.WriteString("-----------\n")
			_ = file.Sync()
			_, _ = file.WriteString(string(debug.Stack()))
			_ = file.Close()
		}

		fmt.Printf("write log panic %v\n", r)

		if err, ok := r.(error); ok {
			return err
		}
	}

	return nil
}

func init() {
	asyncLog = &LogType{
		files: make(map[string]*LogFile),
	}

	timer := time.NewTicker(time.Millisecond * 100)
	//timer := time.NewTicker(time.Second)
	//revive:disable-next-line
	go func() {
		defer func() {
			_ = rec(recover())
			panic("panic in loop asyncfile log flush")
		}()
		for {
			select {
			case <-timer.C:
				asyncLog.RLock()
				for _, file := range asyncLog.files {
					if atomic.LoadInt32(&file.sync.status) != int32(statusDoing) {
						//revive:disable-next-line
						go file.flush2()
					}
				}
				asyncLog.RUnlock()
			}
		}

	}()
}

// NewLogFile 创建日志文件对象
func NewLogFile(filename string) *LogFile {
	asyncLog.Lock()
	defer asyncLog.Unlock()

	if lf, ok := asyncLog.files[filename]; ok {
		return lf
	}

	lf := &LogFile{
		filename:   filename,
		flag:       StdFlag,
		newlinestr: newlineStr,
	}

	asyncLog.files[filename] = lf

	// 默认按小时切割文件
	lf.logRotate.rotate = RotateNone
	lf.logRotate.file = nil
	lf.logRotate.fw = nil

	// 默认开启缓存，满了丢日志
	lf.cache.use = true
	lf.cache.droppable = true
	lf.cache.datach = make(chan string, 100000)

	// 日志写入概率，默认为1.1, 就是全部写入
	lf.probability = 1.1

	// TODO 同步的时间周期，缓存开启才有效
	lf.sync.duration = time.Second
	return lf
}

// SetFlags 设置日志文件输出标识
func (lf *LogFile) SetFlags(flag int) {
	lf.flag = flag
}

// SetRotate 设置日志文件归档切割方式
func (lf *LogFile) SetRotate(rotate LogRotate) {
	lf.logRotate.mutex.Lock()
	defer lf.logRotate.mutex.Unlock()
	lf.logRotate.rotate = rotate
}

// SetUseCache 设置日志文件输出是否使用缓存
func (lf *LogFile) SetUseCache(useCache bool) {
	lf.cache.use = useCache
}

// SetCacheDroppable 设置缓存满了是否能丢弃日志（开启日志该选项才生效）
func (lf *LogFile) SetCacheDroppable(droppable bool) {
	lf.cache.droppable = droppable
}

// SetProbability 设置日志文件输出比率
func (lf *LogFile) SetProbability(probability float32) {
	lf.probability = probability
}

// SetNewLineStr 设置日志文件输出的换行符
func (lf *LogFile) SetNewLineStr(str string) {
	lf.newlinestr = str
}

// Write 写缓存
func (lf *LogFile) Write(msg string) error {
	if lf.flag == StdFlag {
		msg = nowFunc().Format(logTimeFormat) + " " + msg + lf.newlinestr
	} else {
		msg = msg + lf.newlinestr
	}

	if lf.cache.use {
		lf.appendCache2(msg)
		return nil
	}

	return lf.directWrite([]byte(msg))
}

// WriteJson 写入json数据
func (lf *LogFile) WriteJson(data interface{}) error {
	if lf.probability < 1.0 && rand.Float32() > lf.probability {
		// 按照概率写入
		return nil
	}

	bts, err := json.Marshal(data)
	if err != nil {
		return err
	}
	bts = append(bts, newlineChar)

	if lf.cache.use {
		lf.appendCache2(string(bts))
		return nil
	}

	return lf.directWrite(bts)
}

// Flush 刷新缓存日志到文件
func (lf *LogFile) Flush() error {
	return lf.flush2()
}

//*********************** 以下是私有函数 ************************************

func (lf *LogFile) appendCache2(msg string) {
	select {
	case lf.cache.datach <- msg:
	default:
		if !lf.cache.droppable {
			// 不能丢消息，则强等
			lf.cache.datach <- msg
		} else {
			// TODO: 日志丢弃告警
			dropped := atomic.AddInt64(&lf.cache.dropped, 1)
			if dropped%100000 == 1 {
				_, _ = fmt.Fprintf(os.Stderr, "%d log dropped\n", dropped)
			}
		}
	}
}

func (lf *LogFile) flush2() error {

	defer func() {
		_ = rec(recover())
	}()

	atomic.StoreInt32(&lf.sync.status, int32(statusDoing))
	defer func() {
		atomic.StoreInt32(&lf.sync.status, int32(statusDone))
	}()

	// 写入log文件
	file, err := lf.openFileNoCache()
	if err != nil {
		panic(err)
	}
	defer file.Close()

	count := 0
	for {
		count++
		if count > cap(lf.cache.datach) {
			return nil
		}
		select {
		case msg := <-lf.cache.datach:
			_, err = file.WriteString(msg)
			if err != nil {
				return err
			}
		default:
			return nil
		}
	}
}

// 获取文件名的后缀
func (lf *LogFile) getFilenameSuffix() string {
	lf.logRotate.mutex.Lock()
	defer lf.logRotate.mutex.Unlock()
	if lf.logRotate.rotate == RotateNone {
		return ""
	}
	if lf.logRotate.rotate == RotateDate {
		return nowFunc().Format("20060102")
	}
	return nowFunc().Format("2006010215")
}

// 直接写入日志文件
func (lf *LogFile) directWrite(msg []byte) error {
	file, err := lf.openFile()
	if err != nil {
		panic(err)
	}

	lf.logRotate.mutex.Lock()
	_, err = file.Write(msg)
	lf.logRotate.mutex.Unlock()

	return err
}

// 打开日志文件
func (lf *LogFile) openFile() (*os.File, error) {
	suffix := lf.getFilenameSuffix()
	logFilename := lf.filename
	if suffix != "" {
		logFilename = lf.filename + "." + suffix
	}

	lf.logRotate.mutex.Lock()
	defer lf.logRotate.mutex.Unlock()

	if suffix == lf.logRotate.suffix && lf.logRotate.file != nil {
		// check file status events
		if lf.logRotate.fw == nil {
			return lf.logRotate.file, nil
		}
		select {
		case event := <-lf.logRotate.fw.Events:
			if event.Op != fsnotify.Create && event.Op != fsnotify.Remove && event.Op != fsnotify.Rename {
				// file still exists
				return lf.logRotate.file, nil
			}
		default:
			return lf.logRotate.file, nil
		}
	}

	file, err := os.OpenFile(logFilename, fileFlag, fileOpenMode)
	if err != nil {
		// 重试
		file, err = os.OpenFile(logFilename, fileFlag, fileOpenMode)
		if err != nil {
			return file, err
		}
	}

	fw, err := fsnotify.NewWatcher()
	if err != nil {
		fmt.Println("new file status watcher err")
		return nil, err
	}
	fw.Add(file.Name())
	fmt.Println("open log file:", file.Name())

	// 关闭旧的文件
	if lf.logRotate.file != nil {
		_ = lf.logRotate.file.Close()
		_ = lf.logRotate.fw.Close()
	}

	lf.logRotate.file = file
	lf.logRotate.suffix = suffix
	lf.logRotate.fw = fw
	return file, nil
}

// 打开日志文件(不缓存句柄)
func (lf *LogFile) openFileNoCache() (*os.File, error) {
	logFilename := lf.filename
	suff := lf.getFilenameSuffix()
	if suff != "" {
		logFilename = lf.filename + "." + suff
	}

	lf.logRotate.mutex.Lock()
	defer lf.logRotate.mutex.Unlock()

	file, err := os.OpenFile(logFilename, fileFlag, fileOpenMode)
	if err != nil {
		// 重试
		file, err = os.OpenFile(logFilename, fileFlag, fileOpenMode)
		if err != nil {
			return file, err
		}
	}

	return file, nil
}
